﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Linq;
using System.IO;
using MergeAndExecute._0;
using log4net;
using log4net.Config;

namespace FilesMerger2._0
{
    public partial class ExecuteAndCompareForm : Form
    {
        private Dictionary<Int32, String> chkFiles;
        private Dictionary<Int32, String> factFiles;
        private Dictionary<Int32, List<String>> compareFields;
        private Dictionary<Int32, String> sheetNames;
        private List<Int32> selectedIndex;
        private Boolean silent;
        public static readonly ILog Log = LogManager.GetLogger(typeof(ExecuteAndCompareForm));

        public ExecuteAndCompareForm()
        {
            XmlConfigurator.Configure();
            chkFiles = new Dictionary<Int32, String>();
            factFiles = new Dictionary<Int32, String>();
            compareFields = new Dictionary<Int32, List<String>>();
            sheetNames = new Dictionary<Int32, String>();
            InitializeComponent();
            lstCHKFiles.SelectionMode = SelectionMode.MultiExtended;
            lstCHKFiles.SelectedIndexChanged += new EventHandler(lstCHKFiles_SelectedIndexChanged);
            selectedIndex = new List<Int32> { 0 };
            silent = false;
            toolStripStatusLabel1.Text = "Ready";
            toolStripStatusLabel2.Text = String.Empty;
            toolStripProgressBar1.Visible = false;

        }

        private void lstCHKFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (
                lstFactFiles.Items.Count >= lstCHKFiles.Items.Count &&
                !lstCHKFiles.SelectedIndices.Cast<Int32>().ToList().AreEqual(lstFactFiles.SelectedIndices.Cast<Int32>().ToList()) &&
                !silent)
            {
                selectedIndex = lstCHKFiles.SelectedIndices.Cast<Int32>().ToList();
                for (Int32 i = 0; i < lstFactFiles.Items.Count; i++)
                    ChangeSelectionSilent(lstFactFiles, i, false);
                lstCHKFiles.SelectedIndices.Cast<Int32>().ToList().ForEach(x => ChangeSelectionSilent(lstFactFiles, x, true));
                if (compareFields.ContainsKey(selectedIndex.FirstOrDefault()))
                    UpdateListBoxSource(lstCompareColumns, compareFields[selectedIndex.FirstOrDefault()]);
                else
                {
                    UpdateListBoxSource(lstCompareColumns, new List<string>());
                }
                if (sheetNames.ContainsKey(selectedIndex.FirstOrDefault()))
                    txtSheetName.Text = sheetNames[selectedIndex.FirstOrDefault()];
                else
                    txtSheetName.Text = String.Empty;
            }
        }

        private void lstFactFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lstCHKFiles.Items.Count >= lstFactFiles.Items.Count &&
                !lstCHKFiles.SelectedIndices.Cast<Int32>().ToList().AreEqual(lstFactFiles.SelectedIndices.Cast<Int32>().ToList()) &&
                !silent)
            {
                selectedIndex = lstFactFiles.SelectedIndices.Cast<Int32>().ToList();
                for (Int32 i = 0; i < lstCHKFiles.Items.Count; i++)
                    ChangeSelectionSilent(lstCHKFiles, i, false);
                lstFactFiles.SelectedIndices.Cast<Int32>().ToList().ForEach(x => ChangeSelectionSilent(lstCHKFiles, x, true));

                if (compareFields.ContainsKey(selectedIndex.FirstOrDefault()))
                    UpdateListBoxSource(lstCompareColumns, compareFields[selectedIndex.FirstOrDefault()]);
                else
                {
                    UpdateListBoxSource(lstCompareColumns, new List<string>());
                }
                if (sheetNames.ContainsKey(selectedIndex.FirstOrDefault()))
                    txtSheetName.Text = sheetNames[selectedIndex.FirstOrDefault()];
                else
                    txtSheetName.Text = String.Empty;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            AddFileAction(lstCHKFiles, chkFiles);
        }

        private void AddFileAction(ListBox list, Dictionary<Int32, String> storage)
        {
            var openFileDialog = new OpenFileDialog();
            openFileDialog.Multiselect = true;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                AddFileNames(list, openFileDialog.FileNames.ToList(), storage);
            }
        }

        private void MoveUpAction(ListBox list, Dictionary<Int32, String> storage)
        {

            if (list.SelectedIndices.Cast<Int32>().Min() > 0 && (list.SelectedIndices.Cast<Int32>().Max() - list.SelectedIndices.Cast<Int32>().Min() == list.SelectedIndices.Count - 1))
            {
                var selectedIndeces = list.SelectedIndices.Cast<Int32>().ToList();
                var newList = new Dictionary<Int32, String>();

                storage.Where(x => x.Key + 1 < list.SelectedIndices.Cast<Int32>().Min())
                    .ToList().ForEach(y => newList.Add(y.Key, y.Value));
                list.SelectedIndices.Cast<Int32>().ToList().ForEach(x => newList.Add(x - 1, storage[x]));

                newList.Add(newList.Max(x => x.Key) + 1, storage[list.SelectedIndices.Cast<Int32>().Min() - 1]);
                storage.Where(x => x.Key > list.SelectedIndices.Cast<Int32>().Max())
                    .ToList().ForEach(y => newList.Add(y.Key, y.Value));
                storage.Clear();
                newList.ToList().ForEach(x => storage.Add(x.Key, x.Value));
                UpdateListBoxSource(list, storage);
                list.SetSelected(0, false);
                selectedIndeces.ToList().ForEach(x => list.SetSelected(x - 1, true));
                //storage.Select(x => x.Key > list.SelectedIndices.Cast<Int32>().Min());
            }
        }

        private void MoveDownAction(ListBox list, Dictionary<Int32, String> storage)
        {
            if (list.SelectedIndices.Cast<Int32>().Max() < list.Items.Count - 1 && (list.SelectedIndices.Cast<Int32>().Max() - list.SelectedIndices.Cast<Int32>().Min() == list.SelectedIndices.Count - 1))
            {
                var selectedIndeces = list.SelectedIndices.Cast<Int32>().ToList();
                var newList = new Dictionary<Int32, String>();

                storage.Where(x => x.Key < list.SelectedIndices.Cast<Int32>().Min())
                    .ToList().ForEach(y => newList.Add(y.Key, y.Value));


                newList.Add(newList.Any() ? newList.Max(x => x.Key) + 1 : 0, storage[list.SelectedIndices.Cast<Int32>().Max() + 1]);

                list.SelectedIndices.Cast<Int32>().ToList().ForEach(x => newList.Add(x + 1, storage[x]));

                storage.Where(x => x.Key > list.SelectedIndices.Cast<Int32>().Max() + 1)
                    .ToList().ForEach(y => newList.Add(y.Key, y.Value));
                storage.Clear();
                newList.ToList().ForEach(x => storage.Add(x.Key, x.Value));
                UpdateListBoxSource(list, storage);
                list.SetSelected(0, false);
                selectedIndeces.ToList().ForEach(x => list.SetSelected(x + 1, true));
                //storage.Select(x => x.Key > list.SelectedIndices.Cast<Int32>().Min());
            }
        }

        private void RemoveAction(ListBox list, Dictionary<Int32, String> storage)
        {
            var newList = new Dictionary<Int32, String>();
            storage.Where(x => x.Key < list.SelectedIndices.Cast<Int32>().Min())
                .ToList().ForEach(y => newList.Add(y.Key, y.Value));
            storage.Where(x => x.Key > list.SelectedIndices.Cast<Int32>().Max())
                .ToList().ForEach(y => newList.Add(y.Key - list.SelectedIndices.Count, y.Value));
            storage.Clear();
            newList.ToList().ForEach(x => storage.Add(x.Key, x.Value));
            UpdateListBoxSource(list, storage);

        }

        private void AddFileNames(ListBox list, List<String> source, Dictionary<Int32, String> storage)
        {
            source.ForEach(x =>
                    {
                        storage.Add(storage.Any() ? storage.Max(y => y.Key) + 1 : 0, x);
                    });
            UpdateListBoxSource(list, storage);

        }

        private void UpdateListBoxSource(ListBox list, Dictionary<Int32, String> source)
        {
            list.DataSource = source.OrderBy(x => x.Key).Select(x => Path.GetFileName(x.Value)).ToList();
        }

        private void UpdateListBoxSource(ListBox list, List<String> source)
        {
            list.DataSource = source.Select(x => x).ToList();
            list.Refresh();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            AddFileAction(lstFactFiles, factFiles);
        }

        private void btnUp_Click(object sender, EventArgs e)
        {
            MoveUpAction(lstCHKFiles, chkFiles);
        }

        private void btnDown_Click(object sender, EventArgs e)
        {
            MoveDownAction(lstCHKFiles, chkFiles);
        }

        private void btnUpFact_Click(object sender, EventArgs e)
        {
            MoveUpAction(lstFactFiles, factFiles);
        }

        private void btnDownFact_Click(object sender, EventArgs e)
        {
            MoveDownAction(lstFactFiles, factFiles);
        }

        private void btnAddCompareField_Click(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(txtCompareField.Text))
            {
                foreach (Int32 index in lstCHKFiles.SelectedIndices)
                {
                    foreach (var compareField in txtCompareField.Text.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        if (!compareFields.ContainsKey(index))
                            compareFields.Add(index, new List<String>());
                        if (!compareFields[index].Contains(compareField.Trim()))
                            compareFields[index].Add(compareField.Trim());
                    }
                }
                UpdateListBoxSource(lstCompareColumns, compareFields[selectedIndex.FirstOrDefault()]);
                txtCompareField.Text = String.Empty;
                txtCompareField.Focus();
            }
        }

        private void ChangeSelectionSilent(ListBox list, Int32 index, Boolean value)
        {
            silent = true;
            list.SetSelected(index, value);
            silent = false;
        }

        private void btnRemoveCompareField_Click(object sender, EventArgs e)
        {
            lstCompareColumns.SelectedIndices.Cast<Int32>().ToList().ForEach(compareFields[selectedIndex.FirstOrDefault()].RemoveAt);
            UpdateListBoxSource(lstCompareColumns, compareFields[selectedIndex.FirstOrDefault()]);
        }

        private void toolStripStatusLabel2_Click(object sender, EventArgs e)
        {

        }

        private void btnExecute_Click(object sender, EventArgs e)
        {
            Log.Debug("Execution started");
            if (chkFiles.Count != factFiles.Count)
            {
                if (chkFiles.Count > factFiles.Count)
                    chkFiles.Skip(factFiles.Count).ToList().ForEach(x => Log.DebugFormat("File {0} skipped", x));
                else
                    factFiles.Skip(chkFiles.Count).ToList().ForEach(x => Log.DebugFormat("File {0} skipped", x));
            }
            var source = chkFiles.Count > factFiles.Count ? factFiles : chkFiles;
            var saveDialog = new SaveFileDialog
                                 {
                                     AddExtension = true,
                                     DefaultExt = "xls",
                                     Filter = "*Excel file (*.xls)|*.xls",
                                     OverwritePrompt = false
                                 };

            if (saveDialog.ShowDialog() == DialogResult.OK)
            {
                if(IsFileLocked(new FileInfo(saveDialog.FileName)))
                {
                    RtbLog(String.Format("File {0} is used by another process.", saveDialog.FileName));
                    return;
                }
                
                pnMain.Enabled = false;
                toolStripProgressBar1.Visible = true;
                toolStripStatusLabel1.Text = "In Process";
                toolStripProgressBar1.Maximum = source.Count * 3;
                var execThread = new Thread(() =>
                {
                    foreach (var filePair in source)
                    {
                        try
                        {
                            RtbLog(String.Format("Executing {0}", chkFiles[filePair.Key]));
                            var chkTable = SQLExecutor.Execute(File.ReadAllText(chkFiles[filePair.Key], Encoding.GetEncoding(1251)));
                            this.InvokeSafeAction(this, () => toolStripProgressBar1.Increment(1));
                            RtbLog(String.Format("Executing {0}", factFiles[filePair.Key]));
                            var factTable = SQLExecutor.Execute(File.ReadAllText(factFiles[filePair.Key], Encoding.GetEncoding(1251)));
                            this.InvokeSafeAction(this, () => toolStripProgressBar1.Increment(1));
                            RtbLog(String.Format("Comparing {0} and {1}", chkFiles[filePair.Key], factFiles[filePair.Key]));//rtbLog.InvokeSafeAction(this, () => rtbLog.AppendText(String.Format("{0}: Comparing {1} and {2}\n", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"), chkFiles[filePair.Key], factFiles[filePair.Key])));
                            var compareResult = Comparer.CompareTables(chkTable, factTable, compareFields[filePair.Key]);
                            switch (compareResult.Result)
                            {
                                case DiffType.Equal:
                                    RtbLog(String.Format("{0} and {1} are equal", factFiles[filePair.Key], chkFiles[filePair.Key]), Color.Green);
                                    break;
                                case DiffType.Different:
                                    RtbLog(String.Format("{0} and {1} are not equal", factFiles[filePair.Key], chkFiles[filePair.Key]), Color.Red);
                                    break;
                                case DiffType.ColumnMismatch:
                                    RtbLog(String.Format("There is column mismatch in {0} and {1}", factFiles[filePair.Key], chkFiles[filePair.Key]), Color.Red);
                                    break;
                                case DiffType.RowCountMismatch:
                                    RtbLog(String.Format("There is row count mismatch in {0} and {1}", factFiles[filePair.Key], chkFiles[filePair.Key]), Color.Red);
                                    break;
                            }
                            Thread.Sleep(1000);
                            this.InvokeSafeAction(this, () => toolStripProgressBar1.Increment(1));
                            var result = MergeWithSpace(chkTable, factTable);
                            var sheetName = (sheetNames.ContainsKey(filePair.Key) && !String.IsNullOrEmpty(sheetNames[filePair.Key])) ? sheetNames[filePair.Key] : "Untitled" + filePair.Key;
                            (Path.GetFileName(chkFiles[filePair.Key]) + " " + Path.GetFileName(factFiles[filePair.Key])).AppendToExcel(saveDialog.FileName, "windows-1251", sheetName, new RowFormat { BackgroundColor = Color.DarkGray, Pattern = "Solid"});
                            result.DataTableToExcel(";", false, saveDialog.FileName, "windows-1251", sheetName, compareResult.EqualityList.Select(x => new RowFormat { BackgroundColor = x ? Color.White : Color.Red, Pattern = x ? "None" : "Solid" }).ToList());
                            String.Empty.AppendToExcel(saveDialog.FileName, "windows-1251", sheetName);
                        }
                        catch (Exception ex)
                        {
                            RtbLog(String.Format("Failed to process {0} and {1}", factFiles[filePair.Key], chkFiles[filePair.Key]), Color.Red);
                            Log.DebugFormat("An exception occured: {0}", ex);
                            break;
                        }
                    }

                    pnMain.InvokeSafeAction(this, () => { pnMain.Enabled = true; });
                    this.InvokeSafeAction(this, () => toolStripProgressBar1.Visible = false);
                    this.InvokeSafeAction(this, () => toolStripProgressBar1.Value = 0);
                    this.InvokeSafeAction(this, () => toolStripStatusLabel1.Text = "Ready");

                });
                execThread.IsBackground = true;
                execThread.Start();
            }

            Log.Debug("Execution finished");
        }

        private void RenameColumns(DataTable table, String postFix)
        {
            foreach (DataColumn column in table.Columns)
            {
                //if (!column.ColumnName.StartsWith("Id"))
                column.ColumnName = column.ColumnName + postFix;
            }
        }

        private void AddIdColumn(DataTable table)
        {
            //table.Columns.Add(new DataColumn("Id"));

            for (Int32 i = 0; i < table.Rows.Count; i++)
            {
                DataRow dataRow = table.Rows[i];
                dataRow["Id"] = i;
            }
            table.Constraints.Clear();
            table.Constraints.Add("PK_IdColumn", table.Columns.Cast<DataColumn>().Where(x => x.ColumnName.StartsWith("Id")).ToArray(), true);
        }

        private DataTable MergeWithSpace(DataTable chk, DataTable fact)
        {
            var result = new DataTable();
            foreach (DataColumn dataColumn in chk.Columns)
            {
                result.Columns.Add(new DataColumn(dataColumn.ColumnName + "_chk", dataColumn.DataType));
            }
            for (Int32 i = 0; i < 3; i++)
                result.Columns.Add(new DataColumn("space_" + i));
            foreach (DataColumn dataColumn in fact.Columns)
            {
                result.Columns.Add(new DataColumn(dataColumn.ColumnName + "_fact", dataColumn.DataType));
            }
            var totalCount = chk.Rows.Count >= fact.Rows.Count ? chk.Rows.Count : fact.Rows.Count;
            for (Int32 i = 0; i < totalCount; i++)
            {
                var newRow = result.NewRow();
                if (chk.Rows.Count > i)
                {
                    foreach (DataColumn dataColumn in chk.Columns)
                    {
                        newRow[dataColumn.ColumnName + "_chk"] = chk.Rows[i][dataColumn.ColumnName];
                    }
                }
                if (fact.Rows.Count > i)
                {
                    foreach (DataColumn dataColumn in fact.Columns)
                    {
                        newRow[dataColumn.ColumnName + "_fact"] = fact.Rows[i][dataColumn.ColumnName];
                    }
                }
                result.Rows.Add(newRow);
            }
            return result;
        }

        private void lstCHKFiles_SelectedIndexChanged_1(object sender, EventArgs e)
        {

        }

        private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
        {

        }

        private void splitContainer1_Panel2_Paint(object sender, PaintEventArgs e)
        {

        }

        private void btnSaveSheetName_Click(object sender, EventArgs e)
        {
            foreach (Int32 index in lstCHKFiles.SelectedIndices)
            {
                if (sheetNames.ContainsKey(index))
                    sheetNames[index] = txtSheetName.Text;
                else
                    sheetNames.Add(index, txtSheetName.Text);
            }
        }

        private void btnRemoveChk_Click(object sender, EventArgs e)
        {
            RemoveAction(lstCHKFiles, chkFiles);
        }

        private void btnRemoveFact_Click(object sender, EventArgs e)
        {
            RemoveAction(lstFactFiles, factFiles);
        }

        private void RtbLog(String content, Color? color = null)
        {
            rtbLog.InvokeSafeAction(this, () => rtbLog.AppendText(String.Format("{0}: {1}\n", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"), content), color));
        }

        private void copyItem_Click(object sender, EventArgs e)
        {
            Clipboard.SetText(String.Join(";", lstCompareColumns.SelectedIndices.Cast<Int32>().ToList().Select(x => lstCompareColumns.Items[x]).ToList()));//.Aggregate(String.Empty, (r, y) => r += y));
        }

        private void toolStripMenuItem1_Click(object sender, EventArgs e)
        {
            rtbLog.Text = String.Empty;
        }

        private void txtSheetName_TextChanged(object sender, EventArgs e)
        {
            foreach (Int32 index in lstCHKFiles.SelectedIndices)
            {
                if (sheetNames.ContainsKey(index))
                    sheetNames[index] = txtSheetName.Text;
                else
                    sheetNames.Add(index, txtSheetName.Text);
            }
        }

        protected Boolean IsFileLocked(FileInfo file)
        {
            FileStream stream = null;

            try
            {
                stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
            }
            catch (IOException)
            {
                return true;
            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }
            return false;
        }

    }
}
